﻿using System.Text;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization;
using System.Runtime.InteropServices;
using System.Security;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.Xml;
using System.ServiceModel.Security;
using System.ServiceModel.Description;
using System;

namespace Tipler.CompressionEncoder
{
    public class CompressionMessageEncoder : MessageEncoder
    {

        private static string GZipContentType = "application/x-gzip";

        private MessageEncoder innerEncoder;


        public override MessageVersion MessageVersion
        {
            get { return this.innerEncoder.MessageVersion; }
        }


        public override string MediaType
        {
            get { return CompressionMessageEncoder.GZipContentType; }
        }

        public override string ContentType
        {
            get { return CompressionMessageEncoder.GZipContentType; }
        }

        //
        internal CompressionMessageEncoder(MessageEncoder messageEncoder)
        {
            if ((messageEncoder == null))
            {
                throw new ArgumentNullException("messageEncoder", "A valid message encoder must be passed to the GZipEncoder");
            }
            this.innerEncoder = messageEncoder;
        }

        //
        private static ArraySegment<byte> CompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager, int messageOffset)
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                memoryStream.Write(buffer.Array, 0, messageOffset);
                using (DeflateStream dfStream = new DeflateStream(memoryStream, CompressionMode.Compress, true))
                {
                    dfStream.Write(buffer.Array, messageOffset, buffer.Count);
                }
                byte[] compressedBytes = memoryStream.ToArray();
                byte[] bufferedBytes = bufferManager.TakeBuffer(compressedBytes.Length);
                Array.Copy(compressedBytes, 0, bufferedBytes, 0, compressedBytes.Length);
                bufferManager.ReturnBuffer(buffer.Array);
                return new ArraySegment<byte>(bufferedBytes, messageOffset, (bufferedBytes.Length - messageOffset));
            }
        }

        //
        private static ArraySegment<byte> DecompressBuffer(ArraySegment<byte> buffer, BufferManager oBufferManager)
        {
            using (MemoryStream memoryStream = new MemoryStream(buffer.Array, buffer.Offset, (buffer.Count - buffer.Offset)))
            {
                MemoryStream decompressedStream = new MemoryStream();
                int totalRead = 0;
                int blockSize = 1024;
                byte[] tempBuffer = oBufferManager.TakeBuffer(blockSize);

                using (DeflateStream dfStream = new DeflateStream(memoryStream, CompressionMode.Decompress))
                {
                    while (true)
                    {
                        int bytesRead = dfStream.Read(tempBuffer, 0, blockSize);
                        if (bytesRead == 0)
                        {
                            break; // TODO: might not be correct. Was : Exit While
                        }
                        decompressedStream.Write(tempBuffer, 0, bytesRead);
                        totalRead = totalRead + bytesRead;
                    }
                }

                oBufferManager.ReturnBuffer(tempBuffer);
                byte[] decompressedBytes = decompressedStream.ToArray();
                byte[] bufferManagerBuffer = oBufferManager.TakeBuffer((decompressedBytes.Length + buffer.Offset));
                Array.Copy(buffer.Array, 0, bufferManagerBuffer, 0, buffer.Offset);
                Array.Copy(decompressedBytes, 0, bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);
                ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);
                oBufferManager.ReturnBuffer(buffer.Array);
                return byteArray;
            }
        }

        public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)
        {
            DeflateStream dfStream = new DeflateStream(stream, CompressionMode.Decompress, true);
            return this.innerEncoder.ReadMessage(dfStream, maxSizeOfHeaders);
        }

        public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
        {
            ArraySegment<byte> decompressedBuffer = default(ArraySegment<byte>);

            decompressedBuffer = CompressionMessageEncoder.DecompressBuffer(buffer, bufferManager);

            Message returnMessage = this.innerEncoder.ReadMessage(decompressedBuffer, bufferManager);

            returnMessage.Properties.Encoder = this;

            int KullaniciHeaderIndex = returnMessage.Headers.FindHeader("KullaniciID", "");

            int IPAdresindex = returnMessage.Headers.FindHeader("IP", "");

            if (KullaniciHeaderIndex != -1 && IPAdresindex != -1)
            {
                int KullaniciID = returnMessage.Headers.GetHeader<int>(KullaniciHeaderIndex);

                string IpAdres = returnMessage.Headers.GetHeader<string>(IPAdresindex);

                System.Threading.Thread.SetData(System.Threading.Thread.GetNamedDataSlot("KullaniciID"), KullaniciID);

                System.Threading.Thread.SetData(System.Threading.Thread.GetNamedDataSlot("IP"), IpAdres);

            }


            return returnMessage;
        }

        public override void WriteMessage(Message message, Stream stream)
        {
            using (DeflateStream dfStream = new DeflateStream(stream, CompressionMode.Compress, true))
            {
                this.innerEncoder.WriteMessage(message, dfStream);
            }

            stream.Flush();
        }

        public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
        {

            if ((System.Threading.Thread.GetData(System.Threading.Thread.GetNamedDataSlot("KullaniciID")) != null))
            {
                MessageHeader KullaniciIDHeader = MessageHeader.CreateHeader("KullaniciID", "", System.Threading.Thread.GetData(System.Threading.Thread.GetNamedDataSlot("KullaniciID")).ToString());
                message.Headers.Add(KullaniciIDHeader);
            }

            ArraySegment<byte> buffer = this.innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);

            ArraySegment<byte> retbuffer = default(ArraySegment<byte>);

            retbuffer = CompressionMessageEncoder.CompressBuffer(buffer, bufferManager, messageOffset);

            return retbuffer;
        }

    }
}
